#include "NoteManager.h"
#include "WStringSupport.h"
#include "StringTools.h"
#include <io.h>

using namespace std;
using namespace strtool;

NoteManager::NoteManager(string filepath, FILETYPE filetype)
{
	type = filetype;
	loadfile(filepath, filetype);
}

NoteManager::~NoteManager()
{
}

NoteManager * NoteManager::create(string filepath, FILETYPE filetype)
{
	auto mgr = new NoteManager(filepath, filetype);
	if (mgr->gameMode == 3 || filetype == FILE_BMS)	return mgr;
	else return nullptr;
}

void NoteManager::loadfile(string filepath, FILETYPE filetype)
{
	//ifstream file;
	file.open(filepath);
	if (file.eof()) return;

	mapfilepath = filepath;
	vector<string> path;
	split(filepath, path, "/");
	for (size_t i = 0; i < path.size() - 1; i++) dataPath += path[i] + "/";
	//string buffer;
	//unsigned char utf8Head[] = { 0xEF, 0xBB, 0xBF };
	switch (filetype)
	{
		case NoteManager::FILE_IDM:
			break;
		case NoteManager::FILE_OSU:
			//getline(file, buffer);
			//if (strcmp(buffer.substr(0, 3).c_str(), "osu") == 0) loadOsuFile();
			//else if (memcmp(buffer.substr(0, 3).c_str(), utf8Head, sizeof(utf8Head)) == 0 && strcmp(buffer.substr(3, 3).c_str(), "osu") == 0) loadOsuFile();
			OsuLoadFile();
			break;
		case NoteManager::FILE_BMS:
			BMSLoadFile();
			break;
		default:
			break;
	}
	file.close();
}

void NoteManager::OsuLoadFile()
{
	string buffer = "";
	getline(file, buffer);
	unsigned char utf8Head[] = { 0xEF, 0xBB, 0xBF };
	auto isOsufile = memcmp(buffer.substr(0, 3).c_str(), utf8Head, sizeof(utf8Head)) == 0;
	isOsufile = isOsufile && strcmp(buffer.substr(3, 3).c_str(), "osu") == 0;
	isOsufile = isOsufile || strcmp(buffer.substr(0, 3).c_str(), "osu") == 0;
	while (!file.eof() && gameMode == 3 && isOsufile)
	{
		if (buffer[0] != '[')
		{
			getline(file, buffer);
			continue;
		}
		if (
			buffer == "[General]" ||
			buffer == "[Editor]" ||
			buffer == "[Metadata]" ||
			buffer == "[Difficulty]"
			)
		{
			OsuLoadFileInfo(buffer);
		}
		else if (buffer == "[Events]") OsuLoadBGFilename(buffer);
		else if (buffer == "[TimingPoints]") OsuLoadFileTiming(buffer);
		else if (buffer == "[HitObjects]") OsuFileLoadNote();
		else
		{
			getline(file, buffer); continue;
		}
	}
}

void NoteManager::OsuLoadFileInfo(string& retBuffer)
{
	string linedata;
	string name;
	string value;
	while (!file.eof())
	{
		getline(file, linedata);
		if (linedata[0] == '[')
		{
			retBuffer = linedata;
			break;
		}
		if (linedata.empty()) continue;
		vector<string> data;
		split(trim(linedata), data, ":");
		if (data.empty()) continue;
		else name = data[0];
		if (data.size() > 1) value = trim(data[1]);
		else value = "";
		if (name.compare("Mode") == 0)
		{
			gameMode = atoi(value.c_str());
			if (gameMode != 3) return;
			else continue;
		}
		if (name == "AudioFilename") { musicFile = dataPath + value; continue; }
		if (name == "PreviewTime") { previewPoint = atoi(value.c_str()); continue; }
		if (name == "BeatDivisor") { beatDivisor = atoi(value.c_str()); continue; }
		if (name == "Title") { metaData.title = value; continue; }
		if (name == "TitleUnicode") { metaData.titleU = value; continue; }
		if (name == "Artist") { metaData.artist = value; continue; }
		if (name == "ArtistUnicode") { metaData.artistU = value; continue; }
		if (name == "Creator") { metaData.noter = value; continue; }
		if (name == "Version") { metaData.version = value; continue; }
		if (name == "Source") { metaData.source = value; continue; }
		if (name == "Tags") { metaData.tag = value; continue; }
		if (name == "BeatmapID") { mapId = atoi(value.c_str()); continue; }
		if (name == "BeatmapSetID") { setId = atoi(value.c_str()); continue; }
		if (name == "CircleSize") { trackCount = atoi(value.c_str()); continue; }
	}
	hash<string> pathhash;
	setId = pathhash(dataPath);
}

void NoteManager::OsuLoadFileTiming(string & retBuffer)
{
	string linedata;
	Timing timing;
	while (!file.eof())
	{
		getline(file, linedata);
		if (linedata[0] == '[')
		{
			retBuffer = linedata;
			break;
		}
		if (linedata.empty()) continue;
		vector<string> data;
		split(trim(linedata), data, ",");
		if (data.empty() || data.size() < 8) continue;
		timing.startTime = atof(data[0].c_str());
		timing.mspb = atof(data[1].c_str());
		timing.beatsPerBar = atoi(data[2].c_str());
		if (timing.mspb > 0)
		{
			timing.bpm = 60000 / timing.mspb;
			if (timing.bpm > bpmMax) bpmMax = timing.bpm;
			if (timing.bpm < bpmMin || bpmMin == 0)bpmMin = timing.bpm;
			timing.speedModer = 1.0;
		}
		else timing.speedModer = 100 / timing.mspb * -1;
		timing.isHighlight = atoi(data[7].c_str()) == 1 ? true : false;
		timingSegs.push_back(timing);
	}
	timingSegsSize = timingSegs.size();
	for (int i = 0; i < timingSegsSize; i++)
	{
		if (i > 0 && timingSegs[i].mspb < 0) timingSegs[i].mspb = timingSegs[i - 1].mspb;
	}
}

void NoteManager::OsuFileLoadNote()
{
	string linedata;
	vector<Note> vNotes;
	Note note;
	int maxPosX = 0;
	while (!file.eof())
	{
		getline(file, linedata);
		if (linedata.empty()) continue;
		vector<string> data;
		split(trim(linedata), data, ",");
		note.positionX = atoi(data[0].c_str());
		if (note.positionX > maxPosX) maxPosX = note.positionX;
		note.startTime = atoi(data[2].c_str());
		char endtime[8];
		sscanf(data[5].c_str(), "%[^:]", endtime);
		note.endTime = atoi(endtime);
		note.noteType = note.endTime ? note.LONG : note.SINGLE;
		vNotes.push_back(note);
	}
	level = OsuGetMapLevel(vNotes);
	float divisor = (float)maxPosX / trackCount;
	trackNotes = new vector<Note>[trackCount];
	for (size_t i = 0; i < vNotes.size(); i++)
	{
		int trackNo = vNotes[i].positionX / divisor;
		trackNotes[trackNo < trackCount ? trackNo : trackCount - 1].push_back(vNotes[i]);
	}
}

int NoteManager::OsuGetMapLevel(vector<Note>& notes)
{
	int noteCount = notes.size();
	if (noteCount < 2) return 0;
	int effectiveTime = 0;
	int effectiveNoteCount = 0;
	int noteInterval;
	//float averageNoteInterval;
	effectiveNoteCount += notes[0].noteType == Note::SINGLE ? 1 : 2;
	for (int i = 1; i < noteCount - 1; i++)
	{
		noteInterval = notes[i + 1].startTime - notes[i].startTime;
		if (noteInterval <= 2000)
		{
			if (notes[i].noteType == Note::SINGLE)
			{
				effectiveTime += noteInterval;
				effectiveNoteCount += 1;
			}
			else
			{
				effectiveTime += noteInterval - 0.5*(notes[i].endTime - notes[i].startTime);
				effectiveNoteCount += 2;
			}
		}
	}
	float nt = (float)effectiveNoteCount / trackCount;
	float tt = effectiveTime / 10000.f;
	//float nt = (float)effectiveNoteCount;
	//float tt = effectiveTime / 10000.f;
	return nt / tt;
}

void NoteManager::OsuLoadBGFilename(string & retBuffer)
{
	string linedata;
	vector<string> para;
	while (!file.eof())
	{
		getline(file, linedata);
		if (linedata.empty()) continue;
		if (linedata[0] == '[')
		{
			retBuffer = linedata;
			break;
		}
		string temp = trim(linedata).substr(0U, linedata.find_first_of(' '));
		if (trim(linedata).substr(0U, linedata.find_first_of(' ')).compare("//Background") == 0)
		{
			getline(file, linedata);
			if (linedata.empty()) continue;
			split(linedata, para);
			bgFile = dataPath + para[2].substr(1, para[2].find_last_not_of('\"'));
		}
	}
}

#define COMPARE(a,b) a.compare(b)==0
int NoteManager::BMSGetNumber(string cnum)
{
	int ret = 0;
	for (int i = 0; i < 2; i++)
	{
		int tmp = cnum[i] < 58 ? cnum[i] - 48 : cnum[i] - 55;
		i == 0 ? ret += tmp * 36 : ret += tmp;
	}
	return ret;
}
void NoteManager::BMSLoadFile()
{
	string buffer;
	hash<string> pathhash;
	setId = pathhash(dataPath);
	mapId = pathhash(mapfilepath);
	trackNotes = new vector<Note>[16];
	wavdefs = new WAVINFO[1296];
	bmpdefs = new string[1296];
	bpmdefs = new float[1296];
	for (int i = 0; i < 1296; i++) bpmdefs[i] = -1;
	bpmdefs[0] = 130;
	auto InfoValue = [](vector<string> data)
	{
		string ret = data[1];
		for (int i = 2; i < (int)data.size(); i++) ret += " " + data[(int)i];
		return ret;
	};
	auto toUTF8 = [](string str)
	{
		auto src = str.c_str();
		DWORD size = MultiByteToWideChar(932, 0, src, -1, NULL, 0);
		wchar_t* unicode = new wchar_t[size];
		if (!unicode) delete[]unicode;
		MultiByteToWideChar(932, 0, src, -1, unicode, size);
		size = WideCharToMultiByte(65001, NULL, unicode, -1, NULL, 0, NULL, FALSE);
		char *utf8 = new char[size];
		if (!utf8) delete[]utf8;
		WideCharToMultiByte(65001, NULL, unicode, -1, utf8, size, NULL, FALSE);
		return string(utf8);
	};
	while (!file.eof())
	{
		getline(file, buffer);
		buffer = trim(buffer);
		if (buffer.size() == 0) continue;
		vector<string> data;
		split(trim(buffer), data, " ");
		if (data[0][0] != '#') continue;
		else if (data.size() < 2U)
		{
			if (data[0].find_first_of(':') == 6U)
			{
				BMSDATA dataFieldData;
				dataFieldData.mensure = atoi(data[0].substr(1, 3).c_str());
				dataFieldData.channel = atoi(data[0].substr(4, 2).c_str());
				dataFieldData.message = data[0].substr(7);
				if (dataFieldData.channel == 1) dataFieldData.channel = 91;
				bmsData.push_back(dataFieldData);
			}
		}
		else
		{
			if (data[0].substr(0, 4) == "#WAV")
			{
				int i = BMSGetNumber(data[0].substr(4U, 2));
				wavdefs[i].filename = data[1];
				wavdefs[i].seqno = wavseq++;
			}
			if (data[0].substr(0, 4) == "#BMP")
			{
				int i = BMSGetNumber(data[0].substr(4U, 2));
				bmpdefs[i] = data[1];
			}
			if (data[0].substr(0, 4) == "#BPM" && data[0].size() > 4)
			{
				int i = BMSGetNumber(data[0].substr(4U, 2));
				bpmdefs[i] = atof(data[1].c_str());
			}
			if (data[0] == "#PLAYER") { trackCount = atoi(data[1].c_str()) == 1 ? 8 : 16; continue; }
			if (data[0] == "#TITLE") { metaData.title = toUTF8(InfoValue(data)); continue; }
			if (data[0] == "#ARTIST") { metaData.artist = toUTF8(InfoValue(data)); continue; }
			if (data[0] == "#PLAYLEVEL") { level = atoi(data[1].c_str()); continue; }
			if (data[0] == "#BPM") { float bpm = atof(data[1].c_str()); bpmdefs[0] = bpmMax = bpmMin = bpm; }
			if (data[0] == "#DIFFICULTY")
			{
				//string diff;
				switch (atoi(data[1].c_str()))
				{
					case 1:
						metaData.version = "Beginner"; break;
					case 2:
						metaData.version = "Normal"; break;
					case 3:
						metaData.version = "Hyper"; break;
					case 4:
						metaData.version = "Another"; break;
					case 5:
						metaData.version = "Insane"; break;
					default:
						break;
				}
				continue;
			}
			if (data[0] == "#RANK" && metaData.version.empty())
			{
				//string diff;
				switch (atoi(data[1].c_str()))
				{
					case 0:
						metaData.version = "VERY HARD"; break;
					case 1:
						metaData.version = "HARD"; break;
					case 2:
						metaData.version = "NORMAL"; break;
					case 3:
						metaData.version = "EASY"; break;
					default:
						break;
				}
				continue;
			}
			if (data[0] == "#STAGEFILE") { bgFile = dataPath + toUTF8(data[1]); continue; }
			if (data[0] == "#BACKBMP") { bgFile = dataPath + toUTF8(data[1]); continue; }
			if (data[0] == "#PREVIEW") { previewFile = dataPath + toUTF8(data[1]); continue; }
			if (data[0] == "#LNTYPE") { bmsLNType = atoi(data[1].c_str()); continue; }
			if (data[0] == "#LNOBJ") { bmsLNObj = data[1]; continue; }
		}
	}
	BMSDataProcess();
}

void NoteManager::BMSDataProcess()
{
	auto splitMsg = [](string msg)
	{
		vector<string> chlmsg;
		for (int i = 0; i < (int)msg.length(); i += 2)
		{
			chlmsg.push_back(msg.substr(i, 2));
		}
		return chlmsg;
	};

	struct DATASEQUENCE
	{
		float mensure;
		int channel;
		string message;
	};
	vector<DATASEQUENCE> dataseq;
	for (auto data : bmsData)
	{
		vector<string> msgs;
		if (data.channel == 2)
			msgs.push_back(data.message);
		else
			msgs = splitMsg(data.message);
		auto msgdecimal = 1.f / msgs.size();
		int msgindex = 0;
		for (auto msg : msgs)
		{
			//if (msg != "00")
			//{
			DATASEQUENCE ds;
			ds.mensure = data.mensure + msgindex*msgdecimal;
			ds.channel = data.channel;
			ds.message = msg;
			dataseq.push_back(ds);
			//}
			msgindex++;
		}
	}
	auto dataSort = [](DATASEQUENCE a, DATASEQUENCE b)
	{
		return a.mensure != b.mensure ? a.mensure < b.mensure : a.channel < b.channel;
	};
	sort(dataseq.begin(), dataseq.end(), dataSort);

	Timing initTiming;
	initTiming.bpm = bpmdefs[0];
	initTiming.mspb = 60000 / bpmdefs[0];
	timingSegs.push_back(initTiming);
	float mtime = 0;
	float mindex = 0;
	float mtimesig = 1.0f;
	float mlength = 4 * initTiming.mspb;
	float mprev = 0;
	struct MessageData
	{
		float offset;
		int value;
	};
	auto procBPM = [&](DATASEQUENCE ds)
	{
		if (ds.message == "00") return;
		Timing tmp;
		tmp.startTime = mtime;
		int val = 0;
		if (ds.channel == 3)
		{
			val = hexstr2int(ds.message);
			tmp.bpm = val > 0 ? val : 180;
		}
		else
		{
			val = BMSGetNumber(ds.message);
			tmp.bpm = bpmdefs[val] < 0 ? 180 : bpmdefs[val];
		}
		tmp.mspb = 60000 / tmp.bpm;
		timingSegs.push_back(tmp);
		if (tmp.bpm > bpmMax) bpmMax = tmp.bpm;
		else if (tmp.bpm < bpmMin) bpmMin = tmp.bpm;
		mlength = 4 * tmp.mspb;
		timingSegsSize++;
	};
	auto procBGM = [&](string msg)
	{
		if (msg == "00") return;
		auto wavidx = BMSGetNumber(msg);
		CHANNELBGMOBJECT tmp;
		tmp.time = mtime;
		tmp.wavseq = wavdefs[wavidx].seqno;
		tmp.filename = wavdefs[wavidx].filename;
		chlBgm.push_back(tmp);
	};
	int* unfinishLN = new int[trackCount];
	for (int i = 0; i < trackCount; i++) unfinishLN[i] = -1;
	auto procNote = [&](DATASEQUENCE ds)
	{
		if (ds.message == "00") return;
		int track = 0;
		bool longnote = false;
		if (ds.channel > 50)
		{
			ds.channel -= 40;
			longnote = true;
		}
		switch (ds.channel)
		{
			case 11:track = 1; break;
			case 12:track = 2; break;
			case 13:track = 3; break;
			case 14:track = 4; break;
			case 15:track = 5; break;
			case 16:track = 0; break;
			case 18:track = 6; break;
			case 19:track = 7; break;
			case 21:track = 8; break;
			case 22:track = 9; break;
			case 23:track = 10; break;
			case 24:track = 11; break;
			case 25:track = 12; break;
			case 26:track = 15; break;
			case 28:track = 13; break;
			case 29:track = 14; break;
			default:
				break;
		}
		Note tmp;
		if (!longnote)
		{
			tmp.startTime = mtime;
			tmp.bmsSoundIndex = wavdefs[BMSGetNumber(ds.message)].seqno;
			trackNotes[track].push_back(tmp);
		}
		else
		{
			if (unfinishLN[track] < 0)
			{
				tmp.noteType = tmp.LONG;
				tmp.startTime = mtime;
				tmp.bmsSoundIndex = wavdefs[BMSGetNumber(ds.message)].seqno;
				trackNotes[track].push_back(tmp);
				unfinishLN[track] = trackNotes[track].size() - 1;
			}
			else
			{
				trackNotes[track][unfinishLN[track]].endTime = mtime;
				unfinishLN[track] = -1;
			}
		}
	};
	for (auto data : dataseq)
	{
		if (data.mensure > mprev)
		{
			int newmindex = (int)data.mensure;
			if (newmindex > mindex)
			{
				mtime += (mindex + 1 - mprev)*mtimesig*mlength;
				mprev = mindex + 1;
				mindex = mindex + 1;
				mtimesig = 1.0f;
			}
			mtime += (data.mensure - mprev)*mtimesig*mlength;
			mprev = data.mensure;
			//if ((int)data.mensure > (int)mprev) mtimesig = 1.0f;
		}
		switch (data.channel)
		{
			case 2:
				mtimesig = atof(data.message.c_str());
				break;
			case 3:
			case 8:
				procBPM(data);
				break;
			case 91:
				procBGM(data.message);
				break;
			default:
				//this is test code
				if ((data.channel > 10 && data.channel < 30) || (data.channel > 50 && data.channel < 70))
					procNote(data);
				break;
		}
	}
	if (timingSegs.size() == 0)
	{
		Timing tmp;
		tmp.startTime = 0;
		tmp.bpm = bpmdefs[0];
		tmp.mspb = 60000 / tmp.bpm;
		timingSegs.push_back(tmp);
		timingSegsSize = 1;
	}
}

int NoteManager::getTotalNotes()
{
	int total = 0;
	for (int i = 0; i < trackCount; i++) total += trackNotes[i].size();
	return total;
}

int NoteManager::getTotalNotes(int & singlecount, int & longcount)
{
	int sc = 0;
	int lc = 0;
	for (int i = 0; i < trackCount; i++)
	{
		//for (vector<Note>::iterator iter = trackNotes[i].begin(); iter != trackNotes[i].end(); iter++)
		for (auto iter : trackNotes[i])
		{
			if (iter.noteType == Note::SINGLE) sc++;
			else lc++;
		}
	}
	singlecount = sc;
	longcount = lc;
	return sc + lc;
}

string NoteManager::getPreviewFileName()
{
	string filename = "";
	switch (type)
	{
		case 1:
			filename = musicFile;
		case 2:
			if (!previewFile.empty())
			{
				if (_access(previewFile.c_str(), 4) >= 0)
					filename = previewFile;
				else
				{
					auto subindex = previewFile.find_last_of('.');
					if (previewFile.substr(subindex, 4) == ".wav")
						filename = previewFile.substr(0, subindex) + ".ogg";
					else
						filename = previewFile.substr(0, subindex) + ".wav";
				}
			}
			break;
		default:
			break;
	}
	return filename;
}
